using System.Collections.Generic;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework;
using System.Globalization;
using Microsoft.Xna.Framework.Content;
using System.Xml;

namespace Tomahawk.Runtime.Input.Pattern
{
    /// <summary>
    /// Event raised when this pattern is matched. It's public to catch the eventss
    /// </summary>
    /// <param name="gameTime">The time when the pattern is matched</param>
    public delegate void PatternMatchedMethod();
    
    /// <summary>
    /// A pattern describing a sequence of states in the gamepad.
    /// </summary>
    public class GamePadPattern
    {
        #region "Pattern properties"

        /// <summary>
        /// Time to match all the states of the pattern. If 0, there is no time out
        /// </summary>
        protected float time; //in milliseconds

        /// <summary>
        /// The list of states of the pattern
        /// </summary>
        protected List<PGamePadState> states = new List<PGamePadState>();

        /// <summary>
        /// The name of the pattern
        /// </summary>
        private string name;

        #endregion

        #region "Pattern Matcher properties"

        /// <summary>
        /// Event raised when this pattern is matched
        /// </summary>
        public event PatternMatchedMethod OnPatternMatched;

        /// <summary>
        /// The last PGamePadState that the user reach in the pattern
        /// </summary>
        private int stateIndex = 0;

        /// <summary>
        /// The time in which the first state of the pattern was reached
        /// </summary>
        private float firstState = 0;

        #endregion

        #region "Constructors"

        /// <summary>
        /// This constructor doesn't set anything. Used when loading from xml
        /// </summary>
        internal GamePadPattern()
        {
        }

        /// <summary>
        /// Consturctor. There is no time out. 
        /// </summary>
        /// <param name="name">The name of the pattern</param>
        internal GamePadPattern(string name)
        {
            this.name = name;
            this.time = 0;
        }

        /// <summary>
        /// Constructor. There is time out
        /// </summary>
        /// <param name="name">The name of the pattern</param>
        /// <param name="time">The time to match the pattern</param>
        internal GamePadPattern(string name, float time)
        {
            this.name = name;
            this.time = time;
        }

        /// <summary>
        /// Constructor. Set the time out and list of states
        /// </summary>
        /// <param name="name">The name of the pattern</param>
        /// <param name="time">The time to match the pattern</param>
        /// <param name="states">The list of states that represent the pattern</param>
        internal GamePadPattern(string name, float time, List<PGamePadState> states)
        {
            this.name = name;
            this.time = time;
            this.states = states;
        }

        #endregion

        #region "getters & setters"

        /// <summary>
        /// The pattern time out. The time in which the pattern must be matched.
        /// </summary>
        public float Time
        {
            get { return time; }
            set { time = value; }
        }

        /// <summary>
        /// The states of the pattern in sequence
        /// </summary>
        public List<PGamePadState> States
        {
            get { return states; }
            set { states = value; }
        }

        /// <summary>
        /// The name of te pattern
        /// </summary>
        public string Name
        {
            get { return name; }
        }

        #endregion

        /// <summary>
        /// Add the next state to reach in the pattern
        /// </summary>
        /// <param name="state">The state</param>
        internal void AddState(PGamePadState state)
        {
            states.Add(state);
        }

        /// <summary>
        /// Remove a state in the pattern
        /// </summary>
        /// <param name="state">The state to remove</param>
        internal void RemoveState(PGamePadState state)
        {
            states.Remove(state);
        }

        /// <summary>
        /// Remove the state in position [index] in the pattern
        /// </summary>
        /// <param name="index"></param>
        internal void RemoveStateAt(int index)
        {
            states.RemoveAt(index);
        }

        /// <summary>
        /// Test if this pattern is matched. It must be called in the Update method each
        /// time you want to test the GamePadState of a player. The pattern is matched if
        /// the gamepad has passed by all states.
        /// </summary>
        /// <param name="state">The state of the player</param>
        /// <param name="gameTime">The time when the match is done. It's useful when the event <see cref="OnPatternMatched"></see> is raised</param>
        /// <returns>True if matched</returns>
        internal bool Match(GamePadState state, float elapsedTotalTime)
        {
            bool matched = false;

            //If the time allowed for this pattern is over, then restart the pattern
            if (time != 0 && stateIndex > 0 && elapsedTotalTime - firstState > time)
            {
                stateIndex = 0;
                firstState = 0;
            }//If reach time-out, also test if state is equal to state 0

            if (stateIndex == 0)
            {
                if (((PGamePadState)states[stateIndex]).Equals(state))
                {
                    firstState = elapsedTotalTime;
                    stateIndex++;
                }
            }
            else
            {
                //If reach this state in time, I will test next state
                if (((PGamePadState)states[stateIndex]).Equals(state))
                {
                    stateIndex++;
                }
            }

            //If all states of the pattern has been matched, then the pattern is matched
            if (stateIndex >= states.Count)
            {
                matched = true;
                stateIndex = 0;
                firstState = 0;
            }

            if (matched && OnPatternMatched != null) OnPatternMatched();
            return matched;
        }

        /// <summary>
        /// Load the members of this object with the data in the xml
        /// </summary>
        /// <param name="TemplateName">The xml file with the pattern info</param>
        internal void Load(string TemplateName, ContentManager contentManager)
        {
            //load the template
            XmlDocument xdoc = contentManager.Load<XmlDocument>(TemplateName);
            XmlElement root = xdoc.DocumentElement;

            //get the name
            name = readXmlField(xdoc, "name").InnerText;

            //get the time to match of the pattern
            if (xdoc.GetElementsByTagName("time").Count != 0)
            {
                time = float.Parse(readXmlField(xdoc, "time").InnerText, CultureInfo.InvariantCulture);
            }

            //get the states of the pattern
            XmlNode states = readXmlField(xdoc, "states");

            //get the info of each state
            int i; XmlNode xmlState; PGamePadState state;
            for (i = 0; i < states.ChildNodes.Count; i++)
            {
                xmlState = states.ChildNodes[i];
                //Read a state
                state = readXmlState(xmlState);

                //Add the state parsed to the list of states of the pattern
                this.states.Add(state);
            }
        }

        /// <summary>
        /// Read a state from a xml node
        /// </summary>
        /// <param name="xmlState">The xml node with the state info</param>
        /// <returns>The state readed from the xml</returns>
        private PGamePadState readXmlState(XmlNode xmlState)
        {
            PGamePadState state = new PGamePadState();
            int i; XmlNode info;
            for (i = 0; i < xmlState.ChildNodes.Count; i++)
            {
                info = xmlState.ChildNodes[i];
                switch (info.Name.ToUpper())
                {
                    case "ISCONNECTED":
                        if (info.InnerText.ToUpper().Equals("TRUE"))
                        {
                            state.IsConnected = true;
                        }
                        else
                        {
                            state.IsConnected = false;
                        }
                        break;
                    case "BUTTONS":
                        readXmlButtonsState(info, state);
                        break;
                    case "DPAD":
                        readXmlDPadState(info, state);
                        break;
                    case "THUMBSTICKS":
                        readXmlThumbsticksState(info, state);
                        break;
                    case "TRIGGERS":
                        readXmlTriggersState(info, state);
                        break;
                }
            }

            return state;
        }

        /// <summary>
        /// Read buttons info from the xmlnode passed
        /// </summary>
        /// <param name="xmlButtons">The xml node to read in</param>
        /// <param name="state">The state where the info set</param>
        private void readXmlButtonsState(XmlNode xmlButtons, PGamePadState state)
        {
            int i; XmlNode info;
            for (i = 0; i < xmlButtons.ChildNodes.Count; i++)
            {
                info = xmlButtons.ChildNodes[i];
                switch (info.Name.ToUpper())
                {
                    case "A":
                        state.Buttons.A = readXmlButtonState(info.InnerText);
                        break;
                    case "B":
                        state.Buttons.B = readXmlButtonState(info.InnerText);
                        break;
                    case "BACK":
                        state.Buttons.Back = readXmlButtonState(info.InnerText);
                        break;
                    case "LEFTSHOULDER":
                        state.Buttons.LeftShoulder = readXmlButtonState(info.InnerText);
                        break;
                    case "LEFTSTICK":
                        state.Buttons.LeftStick = readXmlButtonState(info.InnerText);
                        break;
                    case "RIGHTSHOULDER":
                        state.Buttons.RightShoulder = readXmlButtonState(info.InnerText);
                        break;
                    case "RIGHTSTICK":
                        state.Buttons.RightStick = readXmlButtonState(info.InnerText);
                        break;
                    case "START":
                        state.Buttons.Start = readXmlButtonState(info.InnerText);
                        break;
                    case "X":
                        state.Buttons.X = readXmlButtonState(info.InnerText);
                        break;
                    case "Y":
                        state.Buttons.Y = readXmlButtonState(info.InnerText);
                        break;
                }
            }
        }

        /// <summary>
        /// Read the xml state button and returns the PButtonState value
        /// </summary>
        /// <param name="buttonState">The string that represents the state of the button in the xml</param>
        /// <returns>The PButtonState state</returns>
        private PButtonState readXmlButtonState(string buttonState)
        {
            if (buttonState.ToUpper().Equals("PRESSED"))
            {
                return PButtonState.Pressed;
            }
            else if (buttonState.ToUpper().Equals("RELEASED"))
            {
                return PButtonState.Released;
            }
            else
            {
                throw new System.Exception("The button state '" + buttonState + "' found in the pattern xml template is not defined");
            }
        }

        /// <summary>
        /// Read DPad info from the xml node passed
        /// </summary>
        /// <param name="xmlDPad">The xml dpad info</param>
        /// <param name="state">The state where the info set</param>
        private void readXmlDPadState(XmlNode xmlDPad, PGamePadState state)
        {
            int i; XmlNode info;
            for (i = 0; i < xmlDPad.ChildNodes.Count; i++)
            {
                info = xmlDPad.ChildNodes[i];
                switch (info.Name.ToUpper())
                {
                    case "DOWN":
                        state.DPad.Down = readXmlButtonState(info.InnerText);
                        break;
                    case "LEFT":
                        state.DPad.Left = readXmlButtonState(info.InnerText);
                        break;
                    case "RIGHT":
                        state.DPad.Right = readXmlButtonState(info.InnerText);
                        break;
                    case "UP":
                        state.DPad.Up = readXmlButtonState(info.InnerText);
                        break;
                }
            }
        }

        /// <summary>
        /// Read ThumbSticks info from the xml node passed
        /// </summary>
        /// <param name="xmlThumbSticks">The xml thumbsticks info</param>
        /// <param name="state">The state where the info set</param>
        private void readXmlThumbsticksState(XmlNode xmlThumbSticks, PGamePadState state)
        {
            int i; XmlNode info;
            PGamePadVector v;

            for (i = 0; i < xmlThumbSticks.ChildNodes.Count; i++)
            {
                info = xmlThumbSticks.ChildNodes[i];
                switch (info.Name.ToUpper())
                {
                    case "LEFT":
                        v = readXmlVector(info);
                        state.ThumbSticks.Left.X = v.X;
                        state.ThumbSticks.Left.Y = v.Y;
                        break;
                    case "RIGHT":
                        v = readXmlVector(info);
                        state.ThumbSticks.Right.X = v.X;
                        state.ThumbSticks.Right.Y = v.Y;
                        break;
                }
            }
        }

        /// <summary>
        /// Read the info of the vector represented in this node of the xml
        /// </summary>
        /// <param name="vector">The xml node whith the vector info</param>
        /// <returns>A vector with the info from the xml</returns>
        private PGamePadVector readXmlVector(XmlNode vector)
        {
            int i; XmlNode data;
            PGamePadVector v = new PGamePadVector();
            for (i = 0; i < vector.ChildNodes.Count; i++)
            {
                data = vector.ChildNodes[i];
                switch (data.Name.ToUpper())
                {
                    case "X":
                        v.X = float.Parse(data.InnerText, CultureInfo.InvariantCulture);
                        break;
                    case "Y":
                        v.Y = float.Parse(data.InnerText, CultureInfo.InvariantCulture);
                        break;
                }
            }
            return v;
        }

        /// <summary>
        /// Read triggers info from the xml node passed
        /// </summary>
        /// <param name="xmlState">The xml triggers info</param>
        /// <param name="state">The state where the info set</param>
        private void readXmlTriggersState(XmlNode xmlState, PGamePadState state)
        {
            int i; XmlNode info;
            for (i = 0; i < xmlState.ChildNodes.Count; i++)
            {
                info = xmlState.ChildNodes[i];
                switch (info.Name.ToUpper())
                {
                    case "LEFT":
                        state.Triggers.Left = float.Parse(info.InnerText, CultureInfo.InvariantCulture);
                        break;
                    case "RIGHT":
                        state.Triggers.Right = float.Parse(info.InnerText, CultureInfo.InvariantCulture);
                        break;
                }
            }
        }

        /// <summary>
        /// Read a node from the xml
        /// </summary>
        /// <param name="xdoc">The xml document</param>
        /// <param name="fieldName">The name of the field to reads</param>
        /// <returns></returns>
        private XmlNode readXmlField(XmlDocument xdoc, string fieldName)
        {
            XmlNodeList fields = xdoc.GetElementsByTagName(fieldName);
            if (fields.Count == 0) throw new System.Exception("The " + fieldName + " field from pattern xml template doesn't exist");
            return fields[0];
        }

        /// <summary>
        /// Destroy this pattern
        /// </summary>
        internal void Destroy()
        {
            this.states.Clear();
            this.states = null;
        }

    }
}